#include "mongoose.h"
#include "cJSON.h"
#ifndef free
#define free(x) M_Free(x)
#endif

//#define hex_dump
enum {
    CLI_STEP_SEND_WS_HS_REQ = 0,
    CLI_STEP_SEND_WS_HS_WAIT,
    CLI_STEP_SEND_UPD_REQ,
    CLI_STEP_RECV_SRV_ACK,
    CLI_STEP_SEND_SRV_ACK,
    CLI_STEP_RECV_DATA,
    CLI_STEP_SEND_DATA_ACK,
    CLI_STEP_SEND_CLOSE,
    CLI_STEP_WS_WAIT_CLOSE,
    CLI_STEP_WS_EXIT
};

typedef struct StUpdClientMgr {
    cJSON  *root_server;
    cJSON  *root_client;
    int strategy_from;
    unsigned char json_str[1024];
    int next_step;
    int file_size;
    unsigned char md5[50];
}StUpdClientMgr;
struct StUpdClientMgr gUpdClientMgr;

int cJSON_ReplaceItemInObjectPath(cJSON *object, const char *path, cJSON *newitem);
int network_update(u8 *buf,s32 len, int fsize, int timeout);

int client_strategy_result(char *outstr_json, int len)
{
    struct StUpdClientMgr *pcmgr = &gUpdClientMgr;
    int ret = 0;
    struct StUpdClientMgr *pUpdClientMgr = &gUpdClientMgr;
    if (pUpdClientMgr->root_client && pUpdClientMgr->root_server) {
        cJSON* json_tmp = cJSON_GetObjectItem(pUpdClientMgr->root_server, "FIRMWARE");
        if (json_tmp) {
            cJSON* json_sub = cJSON_GetObjectItem(json_tmp, "MD5");
            if (json_sub) {
                memset(pcmgr->md5, 0, sizeof(pcmgr->md5));
                strcpy((char*)pcmgr->md5,json_sub->valuestring);
                printf("=======>MD5:%s<=======!\r\n",json_sub->valuestring);
            }
            json_sub = cJSON_GetObjectItem(json_tmp, "SIZE");
            if (json_sub) {
                pcmgr->file_size = json_sub->valueint;
                printf("=======>SIZE:%d<=======!\r\n",json_sub->valueint);
            }
        }
        if (1) {
            cJSON_ReplaceItemInObjectPath(pUpdClientMgr->root_server, "/CONTROL/STATUS", cJSON_CreateString("ready"));
            ret = 1;
        }
        else {
            cJSON_ReplaceItemInObjectPath(pUpdClientMgr->root_server, "/CONTROL/STATUS", cJSON_CreateString("close"));
            ret = 0;
        }
    }
    char *json_str = cJSON_PrintUnformatted(pUpdClientMgr->root_server);
    int min = strlen(json_str);
    min = min < len ? min : len;
    if (min >= len) {
        ret = -1;
        return ret;
    }
    memcpy(outstr_json, json_str, min);
    outstr_json[min] = 0;
    //printf("=======json_str=========\r\n%s\r\n", json_str);
    if (json_str) free(json_str);
    return ret;
}

int client_data_ack(char *outstr_json, int len)
{
    int ret = 0;
    struct StUpdClientMgr *pUpdClientMgr = &gUpdClientMgr;
    if (pUpdClientMgr->root_client && pUpdClientMgr->root_server) {
        cJSON_ReplaceItemInObjectPath(pUpdClientMgr->root_server, "/CONTROL/STATUS", cJSON_CreateString("close"));
    }
    char *json_str = cJSON_PrintUnformatted(pUpdClientMgr->root_server);
    int min = strlen(json_str);
    min = min < len ? min : len;
    if (min >= len) {
        ret = -1;
        return ret;
    }
    memcpy(outstr_json, json_str, min);
    outstr_json[min] = 0;
    //printf("=======json_str=========\r\n%s\r\n", json_str);
    if (json_str) free(json_str);
    return ret;
}

int cJSON_strcasecmpxx(const char *s1, const char *s2)
{
    if (!s1)
        return (s1 == s2) ? 0 : 1;
    if (!s2)
        return 1;
    for (; tolower(*s1) == tolower(*s2); ++s1, ++s2)
        if (*s1 == 0)
            return 0;
    return tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2);
}

char* cJSON_strdupxx(const char* str)
{
    size_t len;
    char* copy;

    len = strlen(str) + 1;
    if (!(copy = (char*)malloc(len))) return 0;
    memcpy(copy, str, len);
    return copy;
}

int cJSON_ReplaceItemInObjectPath(cJSON *object, const char *path, cJSON *newitem)
{
    int ret = -1;
    char word[100];
    int len;
    cJSON *ctemp = object;
    int index_1 = 0;
    int index_2 = 0;
    while (path[index_1]) {
        while (path[index_1]) {
            if (path[index_1] == '/') {
                index_1++;
                break;
            }
            index_1++;
        }
        index_2 = index_1;
        while (path[index_2] && path[index_2] != '/') {
            index_2++;
        }
        word[0] = 0;
        if (index_2 > index_1) {
            //word match
            if (index_2 - index_1 >= (int)sizeof(word)) {
                len = sizeof(word) - 1;
            }
            len = index_2 - index_1;
            memcpy(word, &path[index_1], len);
            word[len] = 0;
            printf("==word:%s==\r\n", word);
        }
        if (index_1 == index_2 && path[index_1] == 0)
        {
            len = sizeof(word) - 1;
            len = len <= (int)strlen(path) ? len : (int)strlen(path);
            memcpy(word, path, len);
            word[len] = 0;
            //printf("==word:%s==\r\n", word);
        }
        int i = 0;
        cJSON *c = ctemp->child;
        while (c && cJSON_strcasecmpxx(c->string, word))
            i++, c = c->next;

        if (c) {
            if (path[index_2] == 0) {
                newitem->string = cJSON_strdupxx(word);
                cJSON_ReplaceItemInArray(ctemp, i, newitem);
                ret = 0;
                break;
            }
            else {
                ctemp = c;
            }
        }
        index_1 = index_2;
    }
    return ret;
}

int create_client_upd_json(char *output, int len)
{
    int ret = 0;

    cJSON *root = 0;

    cJSON *product = 0;
    cJSON *firmware = 0;
    cJSON *control = 0;

    char *json_str = 0;
    product = cJSON_CreateObject();
    if (!product) { ret = -1; goto ERROR_RET; }
    cJSON_AddItemToObject(product, "COMPANY", cJSON_CreateString("zhongqin"));
    cJSON_AddItemToObject(product, "CLASS", cJSON_CreateNumber(1));
    cJSON_AddItemToObject(product, "MFG", cJSON_CreateString("20190918"));
    cJSON_AddItemToObject(product, "FLASH", cJSON_CreateString("100k"));
    cJSON_AddItemToObject(product, "RAM", cJSON_CreateString("100k"));

    firmware = cJSON_CreateObject();
    if (!firmware) { ret = -2; goto ERROR_RET; }
    cJSON_AddItemToObject(firmware, "NAME", cJSON_CreateString("YAYFMV0.13"));
    cJSON_AddItemToObject(firmware, "VERSION", cJSON_CreateString("SW:112-0310 HW:113-200"));
    cJSON_AddItemToObject(firmware, "MD5", cJSON_CreateString("1223000"));
    cJSON_AddItemToObject(firmware, "SIZE", cJSON_CreateNumber(102000000));

    control = cJSON_CreateObject();
    if (!control) { ret = -3; goto ERROR_RET; }
    cJSON_AddItemToObject(control, "STRATEGY", cJSON_CreateNumber(1));
    cJSON_AddItemToObject(control, "PATH", cJSON_CreateString("ws://www.djyos.com:8800/upgrate"));
    cJSON_AddItemToObject(control, "TRANSFER", cJSON_CreateString("NET"));
    cJSON_AddItemToObject(control, "STATUS", cJSON_CreateString("init"));
    root = cJSON_CreateObject();
    if (!root) { ret = -4; goto ERROR_RET; }
    cJSON_AddItemToObject(root, "PRODUCT", product);
    cJSON_AddItemToObject(root, "FIRMWARE", firmware);
    cJSON_AddItemToObject(root, "CONTROL", control);

    json_str = cJSON_PrintUnformatted(root);
    int min = strlen(json_str);
    min = min < len ? min : len;
    if (min >= len) {
        ret = -6;
        goto ERROR_RET;
    }
    memcpy(output, json_str, min);
    output[min] = 0;
    //printf("=======json_str=========\r\n%s\r\n", json_str);
    if (json_str) free(json_str);
    cJSON_Delete(root);
    return ret;

ERROR_RET:
    return ret;
}

//static void hex_dump(char *desc, const void *addr, const int len)
//{
//    int i;
//    unsigned char buff[17];
//    unsigned char *pc = (unsigned char*)addr;
//
//    // Output description if given.
//    if (desc != NULL)
//        printf("%s:\r\n", desc);
//
//    // Process every byte in the data.
//    for (i = 0; i < len; i++) {
//        // Multiple of 16 means new line (with line offset).
//
//        if ((i % 16) == 0) {
//            // Just don't print ASCII for the zeroth line.
//            if (i != 0)
//                printf("  %s\r\n", buff);
//
//            // Output the offset.
//            printf("  %04x ", i);
//        }
//
//        // Now the hex code for the specific character.
//        printf(" %02x", pc[i]);
//
//        // And store a printable ASCII character for later.
//        if ((pc[i] < 0x20) || (pc[i] > 0x7e))
//            buff[i % 16] = '.';
//        else
//            buff[i % 16] = pc[i];
//        buff[(i % 16) + 1] = '\0';
//    }
//
//    // Pad out last line if not exactly 16 characters.
//    while ((i % 16) != 0) {
//        printf("   ");
//        i++;
//    }
//
//    // And print the final ASCII bit.
//    printf("  %s\r\n", buff);
//}

int cb_write_from_network(unsigned char* pbuf, int len)
{
    (void)pbuf;
    static int totalsize = 0;
    printf("RECV FRAME DATA: %d!\r\n", len);
    totalsize += len;
    if (len == 0) {
        printf("total size: %d!\r\n", totalsize);
    }
    return 0;
}

void mg_hash_md5_firmare(unsigned char *pbuf, int len, unsigned char *digest, int len_16) {
    (void)len_16;
    cs_md5_ctx md5_ctx;
    cs_md5_init(&md5_ctx);
    cs_md5_update(&md5_ctx, pbuf, len);
    cs_md5_final(digest, &md5_ctx);
}


static void cb_client_ev_handler(struct mg_connection *nc, int ev, void *ev_data) {

    struct websocket_message *wm = (struct websocket_message *) ev_data;
    struct StUpdClientMgr *pUpdClientMgr = 0;

    if (nc->user_data == 0) {
        printf("error: cb_client_ev_handler, nc->user_data is NULL!\r\n");
        return;
    }
    pUpdClientMgr = (struct StUpdClientMgr *)nc->user_data;
    cJSON * cjson = 0;
    switch (ev) {
    case MG_EV_WEBSOCKET_FRAME:
        //hex_dump("MG_EV_WEBSOCKET_FRAME:", wm->data, wm->size);
        if (wm->size >= 0) {
            switch (pUpdClientMgr->next_step) {
            case CLI_STEP_RECV_SRV_ACK:
                cjson = cJSON_Parse((char*)wm->data);
                if (pUpdClientMgr->root_server) {
                    cJSON_Delete(pUpdClientMgr->root_server);
                    pUpdClientMgr->root_server = 0;
                }
                pUpdClientMgr->root_server = cjson;
                pUpdClientMgr->next_step = CLI_STEP_SEND_SRV_ACK;
                break;
            case CLI_STEP_RECV_DATA:
                //printf("RECV FRAME DATA: %d!\r\n", wm->size);
                if (wm->size == 0) { //ended with 0 size;
                    network_update(wm->data,  wm->size, pUpdClientMgr->file_size, 5000);
                    pUpdClientMgr->next_step = CLI_STEP_SEND_DATA_ACK;
                }
                else {
                    network_update(wm->data,  wm->size, pUpdClientMgr->file_size, 5000);
                }
                break;
            default:
                printf("ERROR: MG_EV_WEBSOCKET_FRAME: %d!\r\n", wm->size);
                break;
            }
        }
        break;
    case MG_EV_WEBSOCKET_CONTROL_FRAME:
        //hex_dump("MG_EV_WEBSOCKET_CONTROL_FRAME:", wm->data, wm->size);
        break;
    case MG_EV_CLOSE:
        printf("MG_EV_CLOSE!\r\n");
        pUpdClientMgr->next_step = CLI_STEP_WS_EXIT;
        break;
    case MG_EV_WEBSOCKET_HANDSHAKE_DONE:
        printf("MG_EV_WEBSOCKET_HANDSHAKE_DONE!\r\n");
        pUpdClientMgr->next_step = CLI_STEP_SEND_UPD_REQ;
        break;
    case MG_EV_SEND:
        //printf("MG_EV_SEND, %d\r\n", nc->send_mbuf.len);
        break;
    case MG_EV_RECV:
        //printf("MG_EV_RECV, %d\r\n", nc->recv_mbuf.len);
        break;
    default:
        break;
    }
}

int ws_client(struct mg_mgr *pmgr)
{
    struct StUpdClientMgr *pUpdClientMgr = &gUpdClientMgr;

    int ret = 0;
    struct mg_connection *nc;
    //struct mg_mgr mgr;
    //mg_mgr_init(&mgr, NULL);
    //unsigned char output[2048] = { 0 };
    //unsigned char buf_rd[3200] = { 0 };
    unsigned char *output = 0;
    pUpdClientMgr->next_step = CLI_STEP_SEND_WS_HS_REQ;
    while (1) {
        switch (pUpdClientMgr->next_step) {
        case CLI_STEP_SEND_WS_HS_REQ:
            nc = mg_connect(pmgr, "127.0.0.1:8888", cb_client_ev_handler);
            if (nc) {
                nc->user_data = pUpdClientMgr;
            }
            mg_set_protocol_http_websocket(nc);
            mg_send_websocket_handshake(nc, "/upgrade", NULL);
            pUpdClientMgr->next_step = CLI_STEP_SEND_WS_HS_WAIT;
            break;
        case CLI_STEP_SEND_WS_HS_WAIT:
            break;
        case CLI_STEP_SEND_UPD_REQ:
            output = malloc(2048);
            if (output == 0) {
                pUpdClientMgr->next_step = CLI_STEP_SEND_CLOSE;
                break;
            }
            memset(output, 0, 2048);
            ret = create_client_upd_json((char*)output, 2048);
            mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, output, strlen((char*)output));
            if (output) {
                free(output);
                output = 0;
            }
            pUpdClientMgr->next_step = CLI_STEP_RECV_SRV_ACK;
            break;
        case CLI_STEP_RECV_SRV_ACK:
            break;
        case CLI_STEP_SEND_SRV_ACK:
            ret = client_strategy_result((char*)pUpdClientMgr->json_str, sizeof(pUpdClientMgr->json_str));
            if (ret > 0) { //send update ready to server
                mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, pUpdClientMgr->json_str, strlen((char*)pUpdClientMgr->json_str));
                //prepare to receiver
                pUpdClientMgr->next_step = CLI_STEP_RECV_DATA;
            }
            else if (ret == 0) {//send update close to server
                mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, pUpdClientMgr->json_str, strlen((char*)pUpdClientMgr->json_str));
                pUpdClientMgr->next_step = CLI_STEP_SEND_CLOSE;
            }
            else {
                printf("error: json string too large!\r\n");
                pUpdClientMgr->next_step = CLI_STEP_SEND_CLOSE;
            }
            break;
        case CLI_STEP_RECV_DATA:

            break;
        case CLI_STEP_SEND_DATA_ACK:
            ret = client_data_ack((char*)pUpdClientMgr->json_str, sizeof(pUpdClientMgr->json_str));
            if (ret >= 0) { //send update ready to server
                mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, pUpdClientMgr->json_str, strlen((char*)pUpdClientMgr->json_str));
                //prepare to receiver
                pUpdClientMgr->next_step = CLI_STEP_SEND_CLOSE;
            }
            break;
        case CLI_STEP_SEND_CLOSE:
            mg_send_websocket_frame(nc, WEBSOCKET_OP_CLOSE, NULL, 0);
            pUpdClientMgr->next_step = CLI_STEP_WS_WAIT_CLOSE;
            break;
        case CLI_STEP_WS_WAIT_CLOSE:
            break;
        case CLI_STEP_WS_EXIT:
            printf("info: CLI_STEP_WS_EXIT!\r\n");
            goto END_PROCESS;
            break;
        default:
            break;
        }
        mg_mgr_poll(pmgr, 5000);
    }

END_PROCESS:

    //mg_mgr_free(&mgr);
    return 0;
}

int client_upgrade_net()
{
    //unsigned char output[2048] = { 0 };
    struct StUpdClientMgr *pUpdClientMgr = &gUpdClientMgr;
    struct mg_mgr mgr;
    struct mg_connection *nc;
    const char *local_addr = "192.168.137.1:8888";


    const int m_len = 2048;
    unsigned char *output = output = malloc(m_len);
    if (output == 0) {
        printf("info: client_upgrade_net, malloc 2048 failed!\r\n");
        return -1;
    }
    memset(output, 0, m_len);

    mg_mgr_init(&mgr, NULL);
    int ret = create_client_upd_json((char*)output, m_len/*sizeof(output)*/);
    cJSON* cjson = cJSON_Parse((char*)output);
    if (pUpdClientMgr->root_client) {
        cJSON_Delete(pUpdClientMgr->root_client);
        pUpdClientMgr->root_client = 0;
    }
    pUpdClientMgr->root_client = cjson;

    pUpdClientMgr->next_step = CLI_STEP_SEND_WS_HS_REQ;
    while (1) {
        switch (pUpdClientMgr->next_step) {
        case CLI_STEP_SEND_WS_HS_REQ:
            nc = mg_connect(&mgr, local_addr, cb_client_ev_handler);
            if (nc) {
                nc->user_data = pUpdClientMgr;
            }
            mg_set_protocol_http_websocket(nc);
            mg_send_websocket_handshake(nc, "/upgrade", NULL);
            pUpdClientMgr->next_step = CLI_STEP_SEND_WS_HS_WAIT;
            break;
        case CLI_STEP_SEND_WS_HS_WAIT:
            break;
        case CLI_STEP_SEND_UPD_REQ:
            memset(output, 0, m_len);
            ret = create_client_upd_json((char*)output, m_len/*sizeof(output)*/);
            mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, output, strlen((char*)output));
            pUpdClientMgr->next_step = CLI_STEP_RECV_SRV_ACK;
            break;
        case CLI_STEP_RECV_SRV_ACK:
            break;
        case CLI_STEP_SEND_SRV_ACK:
            ret = client_strategy_result((char*)pUpdClientMgr->json_str, sizeof(pUpdClientMgr->json_str));
            if (ret > 0) { //send update ready to server
                mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, pUpdClientMgr->json_str, strlen((char*)pUpdClientMgr->json_str));
                //prepare to receiver
                pUpdClientMgr->next_step = CLI_STEP_RECV_DATA;
            }
            else if (ret == 0) {//send update close to server
                mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, pUpdClientMgr->json_str, strlen((char*)pUpdClientMgr->json_str));
                pUpdClientMgr->next_step = CLI_STEP_SEND_CLOSE;
            }
            else {
                printf("error: json string too large!\r\n");
                pUpdClientMgr->next_step = CLI_STEP_SEND_CLOSE;
            }
            break;
        case CLI_STEP_RECV_DATA:

            break;
        case CLI_STEP_SEND_DATA_ACK:
            ret = client_data_ack((char*)pUpdClientMgr->json_str, sizeof(pUpdClientMgr->json_str));
            if (ret >= 0) { //send update ready to server
                mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, pUpdClientMgr->json_str, strlen((char*)pUpdClientMgr->json_str));
                //prepare to receiver
                pUpdClientMgr->next_step = CLI_STEP_SEND_CLOSE;
            }
            break;
        case CLI_STEP_SEND_CLOSE:
            mg_send_websocket_frame(nc, WEBSOCKET_OP_CLOSE, NULL, 0);
            pUpdClientMgr->next_step = CLI_STEP_WS_WAIT_CLOSE;
            break;
        case CLI_STEP_WS_WAIT_CLOSE:
            break;
        case CLI_STEP_WS_EXIT:
            printf("info: CLI_STEP_WS_EXIT!\r\n");
            goto END_PROCESS;
            break;
        default:
            break;
        }
        mg_mgr_poll(&mgr, 1000);
    }

END_PROCESS:
    if (output) free(output);
    mg_mgr_free(&mgr);
    return 0;
}
